<html lang="en-US">

<head>
    
<title>Go 并发读写 sync.map 的强大之处 - 花落雨忧</title>

<meta property="og:title" content="Go 并发读写 sync.map 的强大之处 - 花落雨忧">



    



    
    <meta property="description" content="大家好，我是煎鱼。
在之前的 《为什么 Go map 和 slice 是非线程安全的？》 文章中，我们讨论了 Go 语言的 map 和 slice 非线程安全的问题，基于此引申出了 map 的两种目前在业界使用的最多的并发支持的模式。
分别是：
[&amp;hellip;] 原生 map &#43; 互斥锁或读写锁 mutex。
[&amp;hellip;] 标准库 sync.Map（Go1.9及以后）。 &amp;hellip;">
    <meta property="og:description" content="大家好，我是煎鱼。
在之前的 《为什么 Go map 和 slice 是非线程安全的？》 文章中，我们讨论了 Go 语言的 map 和 slice 非线程安全的问题，基于此引申出了 map 的两种目前在业界使用的最多的并发支持的模式。
分别是：
[&amp;hellip;] 原生 map &#43; 互斥锁或读写锁 mutex。
[&amp;hellip;] 标准库 sync.Map（Go1.9及以后）。 &amp;hellip;">
    






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="shortcut icon" href="https://www.lican.site/logo/logo.png" type="image/x-icon" />



<link rel="stylesheet" href="/css/style.min.css" />

<link rel="stylesheet" href="/css/reset.min.css" />




<script src="https://www.lican.site/js/highlight.min.js"></script>

<script>
hljs.configure({ ignoreUnescapedHTML: true })
hljs.highlightAll();
</script>


<script src="https://www.lican.site/js/jquery.min.js"></script>




<link href="https://www.lican.site/css/hugo-code.min.css" rel="stylesheet" />



    <style>
        .post-content img {
            max-width: 400px;
        }
    </style>
</head>

<body id="period" class="home blog">
    <a class="skip-content" href="#main">Press "Enter" to skip to content</a>
    <div id="overflow-container" class="overflow-container">
        <header class="site-header" id="site-header" role="banner">
    <div class="max-width">
        <div id="title-container" class="title-container">
            <div id="site-title" class="site-title"><a href="/">花落雨忧</a></div>
            <p class="tagline">思所及 力所行 方为真.</p>
        </div>
        
        <div id="menu-primary-container" class="menu-primary-container">
            <div class="icon-container">
            </div>
            <div id="menu-primary" class="menu-container menu-primary" role="navigation">
                <nav class="menu">
                    <ul id="menu-primary-items" class="menu-primary-items">
                        
                        
                        <li id="menu-item-0"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/posts" aria-current="page" tabindex="0">首页</a></li>
                        
                        <li id="menu-item-1"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/tech/" aria-current="page" tabindex="1">技术文档</a></li>
                        
                        <li id="menu-item-2"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/article/" aria-current="page" tabindex="2">文章</a></li>
                        
                        <li id="menu-item-3"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/project/" aria-current="page" tabindex="3">项目</a></li>
                        
                        <li id="menu-item-4"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/about/" aria-current="page" tabindex="4">关于</a></li>
                        
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>
        <div id="primary-container" class="primary-container">
            <div class="max-width">
                <section id="main" class="main" role="main">
                    <div id="loop-container" class="loop-container">
                        <div
                            class="post type-post status-publish format-standard hentry entry">
                            <article>
                                <div class="post-container">
                                    <div class="post-header">
                                        <h2 class="post-title">
                                            <a href="/posts/go/sync-map/">Go 并发读写 sync.map 的强大之处</a>
                                        </h2>
                                        
                                        <div class="post-byline">Published on
                                            <a class="date" href="javascript:;">2021/12/31</a>
                                            
                                            
                                            
                                            
                                            
                                        
                                    </div>
                                    <div class="post-content">
                                        <p>大家好，我是煎鱼。</p>
<p>在之前的 《<a href="http://mp.weixin.qq.com/s?__biz=MzUxMDI4MDc1NA==&amp;mid=2247489045&amp;idx=1&amp;sn=197bda427246e16907c7b471a5dc0572&amp;chksm=f9040348ce738a5ebf541954a4de29ce746238ab7f6e5a2af8a1765c5383ad4208f43b2bac4f&amp;scene=21#wechat_redirect">为什么 Go map 和 slice 是非线程安全的？</a>》 文章中，我们讨论了 Go 语言的 map 和 slice 非线程安全的问题，基于此引申出了 map 的两种目前在业界使用的最多的并发支持的模式。</p>
<p>分别是：</p>
<ul>
<li>
<p>原生 map + 互斥锁或读写锁 mutex。</p>
</li>
<li>
<p>标准库 sync.Map（Go1.9及以后）。</p>
</li>
</ul>
<p>有了选择，总是有选择困难症的，这<strong>两种到底怎么选，谁的性能更加的好</strong>？我有一个朋友说 标准库 sync.Map 性能菜的很，不要用。我到底听谁的&hellip;</p>
<p>今天煎鱼就带你揭秘 Go sync.map，我们先会了解清楚什么场景下，Go map 的多种类型怎么用，谁的性能最好！</p>
<p>接着根据各 map 性能分析的结果，针对性的对 sync.map 进行源码解剖，了解 WHY。</p>
<p>一起愉快地开始吸鱼之路。</p>
<h2 id="syncmap-优势">sync.Map 优势</h2>
<p>在 Go 官方文档中明确指出 Map 类型的一些建议：</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6500ee41a08a415ca681fa427f5032b3~tplv-k3u1fbpfcp-zoom-1.image" alt="图片"></p>
<ul>
<li>
<p>多个 goroutine 的并发使用是安全的，不需要额外的锁定或协调控制。</p>
</li>
<li>
<p>大多数代码应该使用原生的 map，而不是单独的锁定或协调控制，以获得更好的类型安全性和维护性。</p>
</li>
</ul>
<p>同时 Map 类型，还针对以下场景进行了性能优化：</p>
<ul>
<li>
<p>当一个给定的键的条目只被写入一次但被多次读取时。例如在仅会增长的缓存中，就会有这种业务场景。</p>
</li>
<li>
<p>当多个 goroutines 读取、写入和覆盖不相干的键集合的条目时。</p>
</li>
</ul>
<p>这两种情况与 Go map 搭配单独的 Mutex 或 RWMutex 相比较，使用 Map 类型可以大大减少锁的争夺。</p>
<h2 id="性能测试">性能测试</h2>
<p>听官方文档介绍了一堆好处后，他并没有讲到缺点，所说的性能优化后的优势又是否真实可信。我们一起来验证一下。</p>
<p>首先我们定义基本的数据结构：</p>
<pre><code>// 代表互斥锁
type FooMap struct {
 sync.Mutex
 data map[int]int
}

// 代表读写锁
type BarRwMap struct {
 sync.RWMutex
 data map[int]int
}

var fooMap *FooMap
var barRwMap *BarRwMap
var syncMap *sync.Map

// 初始化基本数据结构
func init() {
 fooMap = &amp;FooMap{data: make(map[int]int, 100)}
 barRwMap = &amp;BarRwMap{data: make(map[int]int, 100)}
 syncMap = &amp;sync.Map{}
}

</code></pre><p>在配套方法上，常见的增删改查动作我们都编写了相应的方法。用于后续的压测（只展示部分代码）：</p>
<pre><code>func builtinRwMapStore(k, v int) {
 barRwMap.Lock()
 defer barRwMap.Unlock()
 barRwMap.data[k] = v
}

func builtinRwMapLookup(k int) int {
 barRwMap.RLock()
 defer barRwMap.RUnlock()
 if v, ok := barRwMap.data[k]; !ok {
  return -1
 } else {
  return v
 }
}

func builtinRwMapDelete(k int) {
 barRwMap.Lock()
 defer barRwMap.Unlock()
 if _, ok := barRwMap.data[k]; !ok {
  return
 } else {
  delete(barRwMap.data, k)
 }
}

</code></pre><p>其余的类型方法基本类似，考虑重复篇幅问题因此就不在此展示了。</p>
<p>压测方法基本代码如下：</p>
<pre><code>func BenchmarkBuiltinRwMapDeleteParalell(b *testing.B) {
 b.RunParallel(func(pb *testing.PB) {
  r := rand.New(rand.NewSource(time.Now().Unix()))
  for pb.Next() {
   k := r.Intn(100000000)
   builtinRwMapDelete(k)
  }
 })
}

</code></pre><p>这块主要就是增删改查的代码和压测方法的准备，压测代码直接复用的是大白大佬的 go19-examples/benchmark-for-map 项目。</p>
<p>也可以使用 Go 官方提供的 map_bench_test.go，有兴趣的小伙伴可以自己拉下来运行试一下。</p>
<h3 id="压测结果">压测结果</h3>
<p>1）写入：</p>
<table>
<thead>
<tr>
<th>方法名</th>
<th>含义</th>
<th>压测结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>BenchmarkBuiltinMapStoreParalell-4</td>
<td>map+mutex 写入元素</td>
<td>237.1 ns/op</td>
</tr>
<tr>
<td>BenchmarkSyncMapStoreParalell-4</td>
<td>sync.map 写入元素</td>
<td>509.3 ns/op</td>
</tr>
<tr>
<td>BenchmarkBuiltinRwMapStoreParalell-4</td>
<td>map+rwmutex 写入元素</td>
<td>207.8 ns/op</td>
</tr>
</tbody>
</table>
<p>在写入元素上，最慢的是 <code>sync.map</code> 类型，其次是原生 map+互斥锁（Mutex），最快的是原生 map+读写锁（RwMutex）。</p>
<p>总体的排序（从慢到快）为：SyncMapStore &lt; MapStore &lt; RwMapStore。</p>
<p>2）查找：</p>
<table>
<thead>
<tr>
<th>方法名</th>
<th>含义</th>
<th>压测结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>BenchmarkBuiltinMapLookupParalell-4</td>
<td>map+mutex 查找元素</td>
<td>166.7 ns/op</td>
</tr>
<tr>
<td>BenchmarkBuiltinRwMapLookupParalell-4</td>
<td>map+rwmutex 查找元素</td>
<td>60.49 ns/op</td>
</tr>
<tr>
<td>BenchmarkSyncMapLookupParalell-4</td>
<td>sync.map 查找元素</td>
<td>53.39 ns/op</td>
</tr>
</tbody>
</table>
<p>在查找元素上，最慢的是原生 map+互斥锁，其次是原生 map+读写锁。最快的是 <code>sync.map</code> 类型。</p>
<p>总体的排序为：MapLookup &lt; RwMapLookup &lt; SyncMapLookup。</p>
<p>3）删除：</p>
<table>
<thead>
<tr>
<th>方法名</th>
<th>含义</th>
<th>压测结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>BenchmarkBuiltinMapDeleteParalell-4</td>
<td>map+mutex 删除元素</td>
<td>168.3 ns/op</td>
</tr>
<tr>
<td>BenchmarkBuiltinRwMapDeleteParalell-4</td>
<td>map+rwmutex 删除元素</td>
<td>188.5 ns/op</td>
</tr>
<tr>
<td>BenchmarkSyncMapDeleteParalell-4</td>
<td>sync.map 删除元素</td>
<td>41.54 ns/op</td>
</tr>
</tbody>
</table>
<p>在删除元素上，最慢的是原生 map+读写锁，其次是原生 map+互斥锁，最快的是 <code>sync.map</code> 类型。</p>
<p>总体的排序为：RwMapDelete &lt; MapDelete &lt; SyncMapDelete。</p>
<h3 id="场景分析">场景分析</h3>
<p>根据上述的压测结果，我们可以得出 <code>sync.Map</code> 类型：</p>
<ul>
<li>
<p>在读和删场景上的性能是最佳的，领先一倍有多。</p>
</li>
<li>
<p>在写入场景上的性能非常差，落后原生 map+锁整整有一倍之多。</p>
</li>
</ul>
<p>因此在实际的业务场景中。假设是读多写少的场景，会更建议使用 <code>sync.Map</code> 类型。</p>
<p>但若是那种写多的场景，例如多 goroutine 批量的循环写入，那就建议另辟途径了，性能不忍直视（无性能要求另当别论）。</p>
<h2 id="syncmap-剖析">sync.Map 剖析</h2>
<p>清楚如何测试，测试的结果后。我们需要进一步深挖，知其所以然。</p>
<p>为什么 <code>sync.Map</code> 类型的测试结果这么的 “偏科”，为什么读操作性能这么高，写操作性能低的可怕，他是怎么设计的？</p>
<h3 id="数据结构">数据结构</h3>
<p><code>sync.Map</code> 类型的底层数据结构如下：</p>
<pre><code>type Map struct {
 mu Mutex
 read atomic.Value // readOnly
 dirty map[interface{}]*entry
 misses int
}

// Map.read 属性实际存储的是 readOnly。
type readOnly struct {
 m       map[interface{}]*entry
 amended bool
}

</code></pre><ul>
<li>
<p>mu：互斥锁，用于保护 read 和 dirty。</p>
</li>
<li>
<p>read：只读数据，支持并发读取（atomic.Value 类型）。如果涉及到更新操作，则只需要加锁来保证数据安全。</p>
</li>
<li>
<p>read 实际存储的是 readOnly 结构体，内部也是一个原生 map，amended 属性用于标记 read 和 dirty 的数据是否一致。</p>
</li>
<li>
<p>dirty：读写数据，是一个原生 map，也就是非线程安全。操作 dirty 需要加锁来保证数据安全。</p>
</li>
<li>
<p>misses：统计有多少次读取 read 没有命中。每次 read 中读取失败后，misses 的计数值都会加 1。</p>
</li>
</ul>
<p>在 read 和 dirty 中，都有涉及到的结构体：</p>
<pre><code>type entry struct {
 p unsafe.Pointer // *interface{}
}

</code></pre><p>其包含一个指针 p, 用于指向用户存储的元素（key）所指向的 value 值。</p>
<p>在此建议你必须搞懂 read、dirty、entry，再往下看，食用效果会更佳，后续会围绕着这几个概念流转。</p>
<h3 id="查找过程">查找过程</h3>
<p>划重点，Map 类型本质上是有两个 “map”。一个叫 read、一个叫 dirty，长的也差不多：</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3a34f8cbe34f428c93bddfa7d302a2bb~tplv-k3u1fbpfcp-zoom-1.image" alt="图片"></p>
<p>sync.Map 的 2 个 map</p>
<p>当我们从 sync.Map 类型中读取数据时，其会先查看 read 中是否包含所需的元素：</p>
<ul>
<li>
<p>若有，则通过 atomic 原子操作读取数据并返回。</p>
</li>
<li>
<p>若无，则会判断 <code>read.readOnly</code> 中的 amended 属性，他会告诉程序 dirty 是否包含 <code>read.readOnly.m</code> 中没有的数据；因此若存在，也就是 amended 为 true，将会进一步到 dirty 中查找数据。</p>
</li>
</ul>
<p>sync.Map 的读操作性能如此之高的原因，就在于存在 read 这一巧妙的设计，其作为一个缓存层，提供了快路径（fast path）的查找。</p>
<p>同时其结合 amended 属性，配套解决了每次读取都涉及锁的问题，实现了读这一个使用场景的高性能。</p>
<h3 id="写入过程">写入过程</h3>
<p>我们直接关注 <code>sync.Map</code> 类型的 Store 方法，该方法的作用是新增或更新一个元素。</p>
<p>源码如下：</p>
<pre><code>func (m *Map) Store(key, value interface{}) {
 read, _ := m.read.Load().(readOnly)
 if e, ok := read.m[key]; ok &amp;&amp; e.tryStore(&amp;value) {
  return
 }
  ...
}

</code></pre><p>调用 <code>Load</code> 方法检查 <code>m.read</code> 中是否存在这个元素。若存在，且没有被标记为删除状态，则尝试存储。</p>
<p>若该元素不存在或已经被标记为删除状态，则继续走到下面流程：</p>
<pre><code>func (m *Map) Store(key, value interface{}) {
 ...
 m.mu.Lock()
 read, _ = m.read.Load().(readOnly)
 if e, ok := read.m[key]; ok {
  if e.unexpungeLocked() {
   m.dirty[key] = e
  }
  e.storeLocked(&amp;value)
 } else if e, ok := m.dirty[key]; ok {
  e.storeLocked(&amp;value)
 } else {
  if !read.amended {
   m.dirtyLocked()
   m.read.Store(readOnly{m: read.m, amended: true})
  }
  m.dirty[key] = newEntry(value)
 }
 m.mu.Unlock()
}

</code></pre><p>由于已经走到了 dirty 的流程，因此开头就直接调用了 <code>Lock</code> 方法<strong>上互斥锁</strong>，保证数据安全，也是凸显<strong>性能变差的第一幕</strong>。</p>
<p>其分为以下三个处理分支：</p>
<ul>
<li>
<p>若发现 read 中存在该元素，但已经被标记为已删除（expunged），则说明 dirty 不等于 nil（dirty 中肯定不存在该元素）。其将会执行如下操作。</p>
</li>
<li>
<p>将元素状态从已删除（expunged）更改为 nil。</p>
</li>
<li>
<p>将元素插入 dirty 中。</p>
</li>
<li>
<p>若发现 read 中不存在该元素，但 dirty 中存在该元素，则直接写入更新 entry 的指向。</p>
</li>
<li>
<p>若发现 read 和 dirty 都不存在该元素，则从 read 中复制未被标记删除的数据，并向 dirty 中插入该元素，赋予元素值 entry 的指向。</p>
</li>
</ul>
<p>我们理一理，写入过程的整体流程就是：</p>
<ul>
<li>
<p>查 read，read 上没有，或者已标记删除状态。</p>
</li>
<li>
<p>上互斥锁（Mutex）。</p>
</li>
<li>
<p>操作 dirty，根据各种数据情况和状态进行处理。</p>
</li>
</ul>
<p>回到最初的话题，为什么他写入性能差那么多。究其原因：</p>
<ul>
<li>
<p>写入一定要会经过 read，无论如何都比别人多一层，后续还要查数据情况和状态，性能开销相较更大。</p>
</li>
<li>
<p>（第三个处理分支）当初始化或者 dirty 被提升后，会从 read 中复制全量的数据，若 read 中数据量大，则会影响性能。</p>
</li>
</ul>
<p>可得知 <code>sync.Map</code> 类型不适合写多的场景，读多写少是比较好的。</p>
<p>若有大数据量的场景，则需要考虑 read 复制数据时的偶然性能抖动是否能够接受。</p>
<h3 id="删除过程">删除过程</h3>
<p>这时候可能有小伙伴在想了。写入过程，理论上和删除不会差太远。怎么 <code>sync.Map</code> 类型的删除的性能似乎还行，这里面有什么猫腻？</p>
<p>源码如下：</p>
<pre><code>func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
 read, _ := m.read.Load().(readOnly)
 e, ok := read.m[key]
 ...
  if ok {
  return e.delete()
 }
}

</code></pre><p>删除是标准的开场，依然先到 read 检查该元素是否存在。</p>
<p>若存在，则调用 <code>delete</code> 标记为 expunged（删除状态），非常高效。可以明确在 read 中的元素，被删除，性能是非常好的。</p>
<p>若不存在，也就是走到 dirty 流程中：</p>
<pre><code>func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
 ...
 if !ok &amp;&amp; read.amended {
  m.mu.Lock()
  read, _ = m.read.Load().(readOnly)
  e, ok = read.m[key]
  if !ok &amp;&amp; read.amended {
   e, ok = m.dirty[key]
   delete(m.dirty, key)
   m.missLocked()
  }
  m.mu.Unlock()
 }
 ...
 return nil, false
}

</code></pre><p>若 read 中不存在该元素，dirty 不为空，read 与 dirty 不一致（利用 amended 判别），则表明要操作 dirty，上互斥锁。</p>
<p>再重复进行双重检查，若 read 仍然不存在该元素。则调用 delete 方法从 dirty 中标记该元素的删除。</p>
<p>需要注意，出现频率较高的 delete 方法：</p>
<pre><code>func (e *entry) delete() (value interface{}, ok bool) {
 for {
  p := atomic.LoadPointer(&amp;e.p)
  if p == nil || p == expunged {
   return nil, false
  }
  if atomic.CompareAndSwapPointer(&amp;e.p, p, nil) {
   return *(*interface{})(p), true
  }
 }
}

</code></pre><p>该方法都是将 entry.p 置为 nil，并且标记为 expunged（删除状态），而<strong>不是真真正正的删除</strong>。</p>
<p>注：不要误用 <code>sync.Map</code>，前段时间从字节大佬分享的案例来看，他们将一个连接作为 key 放了进去，于是和这个连接相关的，例如：buffer 的内存就永远无法释放了&hellip;</p>
<h2 id="总结">总结</h2>
<p>通过阅读本文，我们明确了 <code>sync.Map</code> 和原生 map +互斥锁/读写锁之间的性能情况。</p>
<p>标准库 <code>sync.Map</code> 虽说支持并发读写 map，但更适用于读多写少的场景，因为他写入的性能比较差，使用时要考虑清楚这一点。</p>
<p>另外我们针对 <code>sync.Map</code> 的性能差异，进行了深入的源码剖析，了解到了其背后快、慢的原因，实现了知其然知其所以然。</p>
<p>经常看到并发读写 map 导致致命错误，实在是令人忧心。大家觉得如果本文不错，欢迎分享给更多的 Go 爱好者 ：）</p>
<h2 id="鼓励">鼓励</h2>
<p>若有任何疑问欢迎评论区反馈和交流，<strong>最好的关系是互相成就</strong>，各位的<strong>点赞</strong>就是<a href="https://github.com/eddycjy">煎鱼</a>创作的最大动力，感谢支持。</p>
<blockquote>
<p>文章持续更新，可以微信搜【脑子进煎鱼了】阅读，本文 <strong>GitHub</strong> <a href="https://github.com/eddycjy/blog">github.com/eddycjy/blog</a> 已收录，欢迎 Star 催更。</p>
</blockquote>
<h2 id="参考">参考</h2>
<ul>
<li>
<p>Package sync</p>
</li>
<li>
<p>踩了 Golang sync.Map 的一个坑</p>
</li>
<li>
<p>go19-examples/benchmark-for-map</p>
</li>
<li>
<p>通过实例深入理解sync.Map的工作原理</p>
</li>
</ul>


                                        
                                        
                                        
                                        <div class="rp4wp-related-posts">
                                            <h3>相关文章</h3>
                                            <ul>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/map-slice-concurrency/">为什么 Go map 和 slice 是非线程安全的？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/gmp-why-p/">经典面试题：你觉得 Go 在什么时候会抢占 P？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/goroutine-leak/">跟面试官聊 Goroutine 泄露的 6 种方法，真刺激！</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/struct-pointer/">你知道 Go 结构体和结构体指针调用有什么区别吗？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/go-tips-goroutineloop/">Go 面试官：单核 CPU，开两个 Goroutine，其中一个死循环，会怎么样？</a>
                                                    </div>
                                                </li>
                                                
                                            </ul>
                                        </div>
                                        
                                        
                                    </div>

                                    
                                    
                                    

                                    
                                    <div class="post-meta">
                                        
                                        
                                        <div class="post-tags">
                                            <ul>
                                            
                                            <li>
                                                <a href="/tags/go" title="View all posts tagged match">go</a>
                                            </li>
                                            
                                            <li>
                                                <a href="/tags/%e9%9d%a2%e8%af%95%e9%a2%98" title="View all posts tagged match">面试题</a>
                                            </li>
                                            
                                            </ul>
                                        </div>
                                        
                                        
                                        <nav class="further-reading">
                                            
                                            <div class="previous">
                                                <span>&lt;&lt; Prev</span>
                                                <a href="https://www.lican.site/posts/why-mq/"
                                                    rel="prev">《漫谈 MQ》要消息队列（MQ）有什么用？</a> </div>
                                            
                                            
                                            <div class="next">
                                                <span>Next >></span>
                                                <a href="https://www.lican.site/posts/go/ternary-operator/">Go 凭什么不支持三元运算符？</a> 
                                            </div>
                                            
                                        </nav>
                                    </div>
                                    
                                    

                                    
                                    
                                    

                                </div>
                            </article>
                        </div>
                    </div>
                </section>
                <aside class="sidebar sidebar-primary" id="sidebar-primary" role="complementary">
    <h1 class="screen-reader-text">Sidebar</h1>
    

    
    
    <section id="text-2" class="widget widget_text">
        <div class="textwidget">
            
            <div id="profile">
                <div id="profile_picture"><img src="https://www.lican.site/logo/logo.png"></div>
                <div id="profile_intro">
                    <p><span class="name">Lican</span></p>
                    <p class="intro">全栈开发者，爱好造轮子。</p>
                </div>
            </div>
            
            <p>
                <script type="text/javascript">
                    (function ($) {
                        $(document).ready(function () {
                            var menuPrimaryContainer = $('#menu-primary-container');
                            var profile = $('#text-2');
                            $('#toggle-navigation').click(function () {
                                if (menuPrimaryContainer.hasClass('open')) {
                                    profile.removeClass('open');
                                } else {
                                    profile.addClass('open');
                                }
                            });
                        });
                    })(jQuery);
                </script>
            </p>
        </div>
    </section>
    
    
    
    
    
    <section id="text-5" class="widget widget_text">
        <h2 class="widget-title">开源项目</h2>
        <div class="textwidget">
            <div id="projects" style="line-height: 22px;">
                
                <a href="https://github.com/idoubi/gonews"
                    target="_blank">gonews</a>: &nbsp;Daily news for golang<br>
                
                <a href="https://github.com/idoubi/sql2struct"
                    target="_blank">sql2struct</a>: &nbsp;Generate go struct according to SQL<br>
                
                <a href="https://github.com/idoubi/goz"
                    target="_blank">goz</a>: &nbsp;Request library used in golang<br>
                
        </div>
    </section>
    
    

    
    
    
    
    

    
    
    

    
    
    

    
    
    
    
</aside>
            </div>
        </div>

        <footer id="site-footer" class="site-footer" role="contentinfo">
    <div class="max-width">
    </div>
    <div class="footer">
        <div id="footercontent">
            © lican.vip All rights reserved<br/>
            Built with Hugo Theme <a href="https://github.com/idoubi/hugo-theme-period" target="_blank">Period</a>
        </div>
    </div>
</footer>

<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?e8351b6d4626d5881d439ea1f6184baa";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>
    
    
    </div>
    
</body>

</html>